﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Graphics;
//using TestProject.MovingPart;


using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace Game2DAnimationAPI
{
    public class Animation
    {

        GraphicsDeviceManager _graphics;
        BasicEffect _effect;
        VertexPositionColorTexture[] _vertexData;
        short[] _indexData;
        Microsoft.Xna.Framework.Matrix _viewMatrix;
        Microsoft.Xna.Framework.Matrix _projectionMatrix;

        //
	    // Конструктор - используется в менеджере загрузки анимации
	    // параметры анимации читаются из XML - можно использовать любой парсер,
	    // который умеет перебирать ноды и читать атрибуты
	    //
        public Animation(XElement xe, Texture2D hTexture, GraphicsDeviceManager graphics)
        {
            _viewMatrix = Microsoft.Xna.Framework.Matrix.Identity;
            _projectionMatrix = Microsoft.Xna.Framework.Matrix.CreateOrthographicOffCenter(0, graphics.GraphicsDevice.DisplayMode.Width, graphics.GraphicsDevice.DisplayMode.Height, 0, -1, 1);
            _graphics = graphics;
            _effect = new BasicEffect(graphics.GraphicsDevice);
            _texture = hTexture;

	        _time = MovingPart.Read(xe, "time", 1);//fatof(xe->Attribute("time"));
	        _pivotPos.x = MovingPart.Read(xe, "pivotX", 0);
	        _pivotPos.y = MovingPart.Read(xe, "pivotY", 0);

            var partList = xe.Elements().ToList();
	        foreach (var element in partList) {
		        _bones.Add(new MovingPart(this, element, hTexture.Width, hTexture.Height));
	        }
	        _subPosition.Unit();
	        _subPosition.Move(-_pivotPos.x, -_pivotPos.y);

            _renderList.OrderBy(o => o.Order());
	        //sort(_renderList.begin(), _renderList.end(), CmpBoneOrder);

            _vertexData = new VertexPositionColorTexture[_renderList.Count() * 4];
            _indexData = new short[_renderList.Count() * 6];
            for (int i = 0; i < _renderList.Count(); ++i)
            {
                _indexData[6 * i + 0] = (short)(4 * i + 0);
                _indexData[6 * i + 1] = (short)(4 * i + 1);
                _indexData[6 * i + 2] = (short)(4 * i + 3);
                _indexData[6 * i + 3] = (short)(4 * i + 1);
                _indexData[6 * i + 4] = (short)(4 * i + 2);
                _indexData[6 * i + 5] = (short)(4 * i + 3);
            }
        }

	    //
	    // Настройить положение анимации на экране, 
	    // "pos" - положение анимации на экране, 
	    // чтобы нарисовать зеркально - ставим "mirror" в "true".
	    // Метод лучше всего вызывать перед каждым вызывом Draw()
	    // исключение может быть, если такая анимация на экране всего одна
	    //
        public void SetPos(FPoint pos, bool mirror)
        {
            _subPosition.Unit();
            _subPosition.Move(-_pivotPos.x, -_pivotPos.y);
            if (mirror)
            {
                _subPosition.Scale(-1, 1);
            }
            _subPosition.Move(pos.x, pos.y);
        }

	    //
	    // Нарисовать анимацию на экране 
	    // "position" - значение времени для анимации из диапазона [0, 1]
	    //
        public void Draw(float position, GraphicsDevice device)
        {
	        if (0 > position) 
            {
                position = 0;
            }
            else if (position > 1)
            {
                position = 1;
            }

	        for(int i = 0; i < _renderList.Count(); i++) 
            {
		        _renderList[i]._visible = false;
	        }

	        // для задания положения все спрайтов использую свой внутренний "стек" матриц
	        Matrix matrix = new Matrix(_subPosition);
	        for(int i = 0; i < _bones.Count(); i++) {
		        _bones[i].PreDraw(position, matrix);
	        }    

            // заполним буфер вершин
            int[] quadNumber = new int[1];
	        for(int i = 0; i < _renderList.Count(); i++) {
                _renderList[i].Draw(_vertexData, quadNumber);
	        }

            // Draw textured boxes
            device.RasterizerState = RasterizerState.CullNone;  // vertex order doesn't matter
            device.BlendState = BlendState.NonPremultiplied;    // use alpha blending
            device.DepthStencilState = DepthStencilState.None;  // don't bother with the depth/stencil buffer
            _effect.View = _viewMatrix;
            _effect.Projection = _projectionMatrix;
            _effect.Texture = _texture;
            _effect.TextureEnabled = true;
            _effect.DiffuseColor = Color.White.ToVector3();
            _effect.CurrentTechnique.Passes[0].Apply();

            device.DrawUserIndexedPrimitives(
                PrimitiveType.TriangleList
                , _vertexData
                , 0, quadNumber[0] * 4
                , _indexData, 0, quadNumber[0] * 2);


        }




	    //
	    // Время полного цикла анимации 
	    // эту функцию нужно использовать в Update для расчета 
	    // значения "position" метода Draw
	    // например, для зацикленой анимации можно считать так:
	    // progress += deltaTime / anim->Time();
	    // if(progress >= 1.f) progress = 0.f;
	    //
        public float Time() { return _time; }

        public void AddBone(MovingPart bone)
        {
            _renderList.Add(bone);
        }

        Texture2D _texture;
	    float _time;
	    FPoint _pivotPos;
        List<MovingPart> _bones = new List<MovingPart>();
	    List<MovingPart> _renderList = new List<MovingPart>();
	    Matrix _subPosition = new Matrix();

    };

    //
    // Пример примитивного менеджера анимаций
    //
    // К конкретных проектах рекомендую использовать его исходники 
    // для встраивания в общую систему работы с ресурсами
    //
    public class AnimationManager {
        static List<string> _files = new List<string>(0);
	    static Dictionary<string, Animation> _animations = new Dictionary<string,Animation>();

	    //
	    // Загрузить файл анимации
	    // в случае успеха - вернет "true"
	    // можно загружать несколько файлов - менеджер "коллекционирует" анимации
	    //
	    public static bool Load(string fileName, ContentManager Content, GraphicsDeviceManager graphics)
        {
	        // загружаем текстуру - записываем указатель на нее в "tex", который используется далее
	        //
	        // начало блока - этот код при портировании надо переписать
	
	        //std::string fullFileName = hge->Resource_MakePath(fileName.c_str());
	        string fullFileName = fileName;
	        for (int i = 0; i < _files.Count(); ++i) {
		        if (_files[i] == fullFileName) { // если это файл уже загружен - ничего не делаем
			        return true;
		        }
	        }

            //Texture2D tex = Content.Load<Texture2D>(fullFileName.Substring(0, fullFileName.Count() - 4) + ".png");
            Texture2D tex = Content.Load<Texture2D>(fullFileName.Substring(0, fullFileName.Count() - 4));

	        //if (tex == 0) { // если текстура не найдена - прерываем загрузку, возвращаем "false"
		    //  return false;
	        //}
	        // конец блока

            var doc = XElement.Load(Content.RootDirectory + "\\" + fileName);
	        /*if (doc) */
            { // если файл существует - читаем XML с описанием анимаций
                var AnimationList = doc.Elements().ToList();
                foreach (var animation in AnimationList) {
			        string id = animation.Attribute("id").Value;
			        // ВНИМАНИЕ - из двух и более анимаций с одинаковыми id - будет загружена только первая
			        if (_animations.ContainsKey(id) == false) {
                        _animations[id] = new Animation(animation, tex, graphics);
			        }
		        }
                fullFileName.Replace('/', '\\');
		        _files.Add(fullFileName);// запоминаем загруженый файл в списке

		        return true;
	        }
            //return false;
        }

	
	    //
	    // Выгрузить все анимации и текстуры из памяти
	    //
        public static void UnloadAll()
        {
            _files.Clear();
            _animations.Clear();
        }

	    //
	    // Получить указатель на анимацию - animationId - это имя анимации в редакторе 
	    // (ВАЖНО нельзя вызывать delete для нее!)
	    //
        public static Animation getAnimation(string animationId)
        {
            if (_animations.ContainsKey(animationId))
            {
                return _animations[animationId];
            }
            //LOG("animation " + animationId + " not found.");
            return _animations.Values.ElementAt(0);
        }

    };
}
